﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data.SQLite;
using System.IO;
using System.Linq;
using System.Text;

namespace DotNetExtensions.Core.SQLite
{
    public class SQLiteConnectionInfo
    {
        public string FileName { get; set; }
        public long Id { get; set; }
        public string ConnectionString { get; set; }
        public SQLiteConnection Connection { get; set; }

        public bool CanUse
        {
            get
            {
                try
                {
                    return Connection != null && Connection.State == System.Data.ConnectionState.Closed;
                }
                catch (Exception)
                {
                    Connection = null;
                    return false;
                }
            }
        }
    }
    public class SQLiteConnectionFactory : Singleton<SQLiteConnectionFactory>
    {
        private readonly ConcurrentDictionary<long, SQLiteConnectionInfo> sqliteConnectionInfos;
        private SQLiteConnectionFactory()
        {
            sqliteConnectionInfos = new ConcurrentDictionary<long, SQLiteConnectionInfo>();
        }

        public string GetFileName(string connectionString)
        {
            if (connectionString.IndexOf(';') > 0)
            {
                // Data Source=xxx.db;xxxx
                string[] splits = connectionString.Split(';');
                foreach (var item in splits)
                {
                    if (item.Replace(" ", "").ToLower().Contains("datasource="))
                    {
                        return item.Substring(item.IndexOf('=') + 1);
                    }
                }
            }
            else if (connectionString.Replace(" ", "").ToLower().Contains("datasource="))
            {
                return connectionString.Substring(connectionString.IndexOf('=') + 1);
            }

            throw new Exception(string.Format("Connection string `{0}` invalid", connectionString));
        }


        public SQLiteConnectionInfo Create(string connectionString,bool autoCreateFile = false)
        {
            lock (mutex)
            {
                SQLiteConnectionInfo connectionInfo = null;

                int limit = sqliteConnectionInfos.Count(c => c.Value.ConnectionString == connectionString);

                if(limit > 250)
                {
                    var invalidConnections = sqliteConnectionInfos.Where(c => c.Value.CanUse == false).Select(c=>c.Key);
                    foreach (var item in invalidConnections)
                    {
                        sqliteConnectionInfos.TryRemove(item, out SQLiteConnectionInfo value);
                    }
                }

                if (limit > 1000) throw new Exception("The number of connections has exceeded the maximum 1000");

                if (limit > 10 && sqliteConnectionInfos.Any(c => c.Value.CanUse && c.Value.ConnectionString == connectionString))
                {
                    connectionInfo = sqliteConnectionInfos.FirstOrDefault(c => c.Value.CanUse && c.Value.ConnectionString == connectionString).Value;
                }

                if (connectionInfo == null)
                {
                    
                    var connection = new SQLiteConnection(connectionString);
                    connectionInfo = new SQLiteConnectionInfo
                    {
                        Connection = connection,
                        Id = Snowflake.Default.NextId(),
                        ConnectionString = connectionString
                    };

                    if (autoCreateFile)
                    {
                        string filename = GetFileName(connectionString);
                        connectionInfo.FileName = filename;
                        using (File.Create(filename)) { };
                    }
                    bool added = sqliteConnectionInfos.TryAdd(connectionInfo.Id, connectionInfo);

                    try
                    {
                        connection.Open();
                    }
                    catch (Exception)
                    {
                        throw;
                    }
                }

                return connectionInfo;
            }
        }

    }
}
